<!DOCTYPE html>
<html lang="en"><head>
    <title>CZH-DEV BLOG</title>
    <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
    <meta content="utf-8" http-equiv="encoding">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="format-detection" content="telephone=no" />
    <meta name="theme-color" content="#000084" />
    <link rel="icon" href="https://czh-dev.gitee.io/czh-blog.gitee.io//favicon.ico">
    <link rel="canonical" href="https://czh-dev.gitee.io/czh-blog.gitee.io/">
    
    
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
    <div class="navbar-inner">
        <div class="container">
            <button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"></button>
            <a class="brand" href="https://czh-dev.gitee.io/czh-blog.gitee.io/">CZH-DEV BLOG</a>
            <div class="nav-collapse collapse">
                <ul class="nav">
                    
                    
                        
                            <li>
                                <a href="/czh-blog.gitee.io/about/">
                                    
                                    <span>About</span>
                                </a>
                            </li>
                        
                    
                        
                            <li>
                                <a href="/czh-blog.gitee.io/post/">
                                    
                                    <span>All posts</span>
                                </a>
                            </li>
                        
                    
                        
                            <li>
                                <a href="/czh-blog.gitee.io/ebook/">
                                    
                                    <span>Resource</span>
                                </a>
                            </li>
                        
                    
                </ul>
            </div>
        </div>
    </div>
</nav><div id="content" class="container">
<div style="display: flex;">
  <div class="row-fluid navmargin">
    <div class="page-header">
      <h1>Spring Cloud Alibaba（Ⅲ） - Sat, Feb 11, 2023</h1>
    </div>
    <p class="lead"></p>
    <h1 id="第三章-服务治理--nacos-discovery">第三章 服务治理  Nacos Discovery</h1>
<h2 id="31-什么是服务治理">3.1 什么是服务治理</h2>
<p>服务治理是微服务架构中最核心最基本的模块。用于实现各个微服务的<strong>自动化注册与发现</strong>。</p>
<p>**服务注册：**在服务治理框架中，都会构建一个注册中心，每个服务单元向注册中心登记自己提供服</p>
<p>务的详细信息。并在注册中心形成一张服务的清单，服务注册中心需要以心跳的方式去监测清单中</p>
<p>的服务是否可用，如果不可用，需要在服务清单中剔除不可用的服务。</p>
<p>**服务发现：**服务调用方向服务注册中心咨询服务，并获取所有服务的实例清单，实现对具体服务实</p>
<p>例的访问。</p>
<p><strong><img src="https://czh-pic.oss-cn-guangzhou.aliyuncs.com/202302111454780.png" alt="image-20201029084800199"></strong></p>
<p>通过上面的调用图会发现，除了微服务，还有一个组件是<strong>服务注册中心</strong>，它是微服务架构非常重要</p>
<p>的一个组件，在微服务架构里主要起到了协调者的一个作用。注册中心一般包含如下几个功能：</p>
<ol>
<li>服务发现：</li>
</ol>
<p>服务注册：保存服务提供者和服务调用者的信息</p>
<p>服务订阅：服务调用者订阅服务提供者的信息，注册中心向订阅者推送提供者的信息</p>
<ol start="2">
<li>服务健康检测</li>
</ol>
<p>检测服务提供者的健康情况，如果发现异常，执行服务剔除</p>
<h2 id="32-常见注册中心">3.2 常见注册中心</h2>
<ul>
<li>
<p><strong>Zookeeper</strong></p>
<p>Zookeeper是一个分布式服务框架，是Apache Hadoop 的一个子项目，它主要是用来解决分布式应用中经常遇到的一些数据管理问题，如：统一命名服务、状态同步服务、集群管理、分布式应用</p>
</li>
</ul>
<p>配置项的管理等。</p>
<ul>
<li>
<p><strong>Eureka</strong></p>
<p>Eureka是Springcloud Netflflix中的重要组件，主要作用就是做服务注册和发现。但是现在已经闭源</p>
</li>
<li>
<p><strong>Consul</strong></p>
<p>Consul是基于GO语言开发的开源工具，主要面向分布式，服务化的系统提供服务注册、服务发现</p>
</li>
</ul>
<p>和配置管理的功能。Consul的功能都很实用，其中包括：服务注册/发现、健康检查、Key/Value</p>
<p>存储、多数据中心和分布式一致性保证等特性。Consul本身只是一个二进制的可执行文件，所以</p>
<p>安装和部署都非常简单，只需要从官网下载后，在执行对应的启动脚本即可。</p>
<ul>
<li>
<p><strong>Nacos</strong></p>
<p>Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它是 SpringCloud Alibaba 组件之一，负责服务注册发现和服务配置。</p>
</li>
</ul>
<h2 id="33-nacos-简介">3.3 Nacos 简介</h2>
<p>Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集，帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。</p>
<p>从上面的介绍就可以看出，Nacos的作用就是一个注册中心，用来管理注册上来的各个微服务。</p>
<p><strong>核心功能点</strong>:</p>
<ul>
<li>
<p><strong>服务注册</strong>:  Nacos Client会通过发送REST请求想Nacos Server注册自己的服务，提供自身的元数据，比如IP地址，端口等信息。Nacos Server接收到注册请求后，就会把这些元数据存储到一个双层的内存Map中。</p>
</li>
<li>
<p><strong>服务心跳</strong>:  在服务注册后，Nacos Client会维护一个定时心跳来维持统治Nacos Server,说明服务一致处于可用状态，防止被剔除，默认5s发送一次心跳</p>
</li>
<li>
<p><strong>服务同步</strong>:  Nacos Server集群之间会相互同步服务实例，用来保证服务信息的一致性。</p>
</li>
<li>
<p><strong>服务发现</strong>： 服务消费者(Nacos Client)在调用服务提供的服务时，会发送一个REST请求给Nacos Server,获取上面注册的服务清单，并且缓存在Nacos Client本地,同时会在Nacos Client本地开启一个定时任务拉取服务最新的注册表信息更新到本地缓存。</p>
</li>
<li>
<p><strong>服务健康检查</strong>:  Nacos Server 会开启一个定时任务来检查注册服务实例的健康情况，对于超过15s没有收到客户端心跳的实例会将他的healthy属性设置为false(客户端服务发现时不会发现)，如果某个实例超过30s没有收到心跳，直接剔除该实例(被剔除的实例如果恢复发送心跳则会重新注册)</p>
</li>
</ul>
<h2 id="34-nacos实战入门">3.4 Nacos实战入门</h2>
<p>接下来，我们就在现有的环境中加入nacos，并将我们的两个微服务注册上去。</p>
<h3 id="341-搭建nacos环境">3.4.1 搭建Nacos环境</h3>
<ol>
<li>
<p>安装Nacos</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>下载地址: https://github.com/alibaba/nacos/releases 
</span></span><span style="display:flex;"><span>下载zip格式的安装包，然后进行解压缩操作,上课使用的Nacos Server版本是1.3.2
</span></span></code></pre></div></li>
<li>
<p>启动Nacos</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#75715e">#切换目录 </span>
</span></span><span style="display:flex;"><span>cd nacos/bin 
</span></span><span style="display:flex;"><span><span style="color:#75715e">#命令启动 </span>
</span></span><span style="display:flex;"><span>startup.cmd -m standalone
</span></span></code></pre></div></li>
<li>
<p>访问Nacos</p>
<p>打开浏览器输入http://localhost:8848/nacos，即可访问服务， 默认密码是nacos/nacos</p>
</li>
</ol>
<p><strong><img src="https://czh-pic.oss-cn-guangzhou.aliyuncs.com/202302111454178.png" alt="image-20201029092937927"></strong></p>
<h3 id="342-将商品服务注册到nacos">3.4.2 将商品服务注册到Nacos</h3>
<p>接下来开始修改 shop-product-server 模块的代码， 将其注册到nacos服务上</p>
<ol>
<li>在pom.xml中添加Nacos的依赖</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#75715e">&lt;!--nacos客户端--&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#f92672">&lt;groupId&gt;</span>com.alibaba.cloud<span style="color:#f92672">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#f92672">&lt;artifactId&gt;</span>spring-cloud-starter-alibaba-nacos-discovery<span style="color:#f92672">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;/dependency&gt;</span>
</span></span></code></pre></div><ol start="2">
<li>
<p>在主类上添加**@EnableDiscoveryClient**注解</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#a6e22e">@SpringBootApplication</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@EnableDiscoveryClient</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">public</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">ProductServer</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">static</span> <span style="color:#66d9ef">void</span> <span style="color:#a6e22e">main</span><span style="color:#f92672">(</span>String<span style="color:#f92672">[]</span> args<span style="color:#f92672">)</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>        SpringApplication<span style="color:#f92672">.</span><span style="color:#a6e22e">run</span><span style="color:#f92672">(</span>ProductServer<span style="color:#f92672">.</span><span style="color:#a6e22e">class</span><span style="color:#f92672">,</span>args<span style="color:#f92672">);</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span></code></pre></div></li>
<li>
<p>在application.yml中添加Nacos服务的地址</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">spring</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">cloud</span>: 
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">nacos</span>: 
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">discovery</span>: 
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">server-addr</span>: <span style="color:#ae81ff">localhost:8848</span>
</span></span></code></pre></div></li>
<li>
<p>启动服务， 观察Nacos的控制面板中是否有注册上来的商品微服务</p>
</li>
</ol>
<p><strong><img src="https://czh-pic.oss-cn-guangzhou.aliyuncs.com/202302111454917.png" alt="image-20201029094004073"></strong></p>
<h3 id="343-将订单服务注册到nacos">3.4.3 将订单服务注册到Nacos</h3>
<p>接下来开始修改 shop-order-server 模块的代码， 将其注册到nacos服务上</p>
<ol>
<li>在pom.xml中添加Nacos的依赖</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#75715e">&lt;!--nacos客户端--&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#f92672">&lt;groupId&gt;</span>com.alibaba.cloud<span style="color:#f92672">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#f92672">&lt;artifactId&gt;</span>spring-cloud-starter-alibaba-nacos-discovery<span style="color:#f92672">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;/dependency&gt;</span>
</span></span></code></pre></div><ol start="2">
<li>
<p>在主类上添加**@EnableDiscoveryClient**注解</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#a6e22e">@SpringBootApplication</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@EnableDiscoveryClient</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">public</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">OrderServer</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">static</span> <span style="color:#66d9ef">void</span> <span style="color:#a6e22e">main</span><span style="color:#f92672">(</span>String<span style="color:#f92672">[]</span> args<span style="color:#f92672">)</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>        SpringApplication<span style="color:#f92672">.</span><span style="color:#a6e22e">run</span><span style="color:#f92672">(</span>OrderServer<span style="color:#f92672">.</span><span style="color:#a6e22e">class</span><span style="color:#f92672">,</span>args<span style="color:#f92672">);</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span></code></pre></div></li>
<li>
<p>在application.yml中添加Nacos服务的地址</p>
</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">spring</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">cloud</span>: 
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">nacos</span>: 
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">discovery</span>: 
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">server-addr</span>: <span style="color:#ae81ff">localhost:8848</span>
</span></span></code></pre></div><ol start="4">
<li>启动服务， 观察Nacos的控制面板中是否有注册上来的订单微服务</li>
</ol>
<p><strong><img src="https://czh-pic.oss-cn-guangzhou.aliyuncs.com/202302111454148.png" alt="image-20201029094943183"></strong></p>
<ol start="5">
<li>修改OrderServiceImpl， 实现微服务调用</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#a6e22e">@Service</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@Slf4j</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">public</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">OrderServiceImpl</span> <span style="color:#66d9ef">implements</span> OrderService <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@Autowired</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> OrderDao orderDao<span style="color:#f92672">;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@Autowired</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> DiscoveryClient discoveryClient<span style="color:#f92672">;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@Autowired</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> RestTemplate restTemplate<span style="color:#f92672">;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@Override</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> Order <span style="color:#a6e22e">createOrder</span><span style="color:#f92672">(</span>Long productId<span style="color:#f92672">,</span>Long userId<span style="color:#f92672">)</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>        log<span style="color:#f92672">.</span><span style="color:#a6e22e">info</span><span style="color:#f92672">(</span><span style="color:#e6db74">&#34;接收到{}号商品的下单请求,接下来调用商品微服务查询此商品信息&#34;</span><span style="color:#f92672">,</span> productId<span style="color:#f92672">);</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">//从nacos中获取服务地址
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        ServiceInstance instance <span style="color:#f92672">=</span> discoveryClient<span style="color:#f92672">.</span>
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">getInstances</span><span style="color:#f92672">(</span><span style="color:#e6db74">&#34;product-service&#34;</span><span style="color:#f92672">).</span><span style="color:#a6e22e">get</span><span style="color:#f92672">(</span>0<span style="color:#f92672">);</span>
</span></span><span style="display:flex;"><span>        String url <span style="color:#f92672">=</span> instance<span style="color:#f92672">.</span><span style="color:#a6e22e">getHost</span><span style="color:#f92672">()+</span><span style="color:#e6db74">&#34;:&#34;</span><span style="color:#f92672">+</span>instance<span style="color:#f92672">.</span><span style="color:#a6e22e">getPort</span><span style="color:#f92672">();</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">//远程调用商品微服务,查询商品信息
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        Product product <span style="color:#f92672">=</span> restTemplate<span style="color:#f92672">.</span><span style="color:#a6e22e">getForObject</span><span style="color:#f92672">(</span>
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;http://&#34;</span><span style="color:#f92672">+</span>url<span style="color:#f92672">+</span><span style="color:#e6db74">&#34;/product/&#34;</span><span style="color:#f92672">+</span>productId<span style="color:#f92672">,</span>Product<span style="color:#f92672">.</span><span style="color:#a6e22e">class</span><span style="color:#f92672">);</span>
</span></span><span style="display:flex;"><span>        log<span style="color:#f92672">.</span><span style="color:#a6e22e">info</span><span style="color:#f92672">(</span><span style="color:#e6db74">&#34;查询到{}号商品的信息,内容是:{}&#34;</span><span style="color:#f92672">,</span> productId<span style="color:#f92672">,</span> JSON<span style="color:#f92672">.</span><span style="color:#a6e22e">toJSONString</span><span style="color:#f92672">(</span>product<span style="color:#f92672">));</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">//创建订单并保存
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        Order order <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> Order<span style="color:#f92672">();</span>
</span></span><span style="display:flex;"><span>        order<span style="color:#f92672">.</span><span style="color:#a6e22e">setUid</span><span style="color:#f92672">(</span>userId<span style="color:#f92672">);</span>
</span></span><span style="display:flex;"><span>        order<span style="color:#f92672">.</span><span style="color:#a6e22e">setUsername</span><span style="color:#f92672">(</span><span style="color:#e6db74">&#34;叩丁狼教育&#34;</span><span style="color:#f92672">);</span>
</span></span><span style="display:flex;"><span>        order<span style="color:#f92672">.</span><span style="color:#a6e22e">setPid</span><span style="color:#f92672">(</span>productId<span style="color:#f92672">);</span>
</span></span><span style="display:flex;"><span>        order<span style="color:#f92672">.</span><span style="color:#a6e22e">setPname</span><span style="color:#f92672">(</span>product<span style="color:#f92672">.</span><span style="color:#a6e22e">getPname</span><span style="color:#f92672">());</span>
</span></span><span style="display:flex;"><span>        order<span style="color:#f92672">.</span><span style="color:#a6e22e">setPprice</span><span style="color:#f92672">(</span>product<span style="color:#f92672">.</span><span style="color:#a6e22e">getPprice</span><span style="color:#f92672">());</span>
</span></span><span style="display:flex;"><span>        order<span style="color:#f92672">.</span><span style="color:#a6e22e">setNumber</span><span style="color:#f92672">(</span>1<span style="color:#f92672">);</span>
</span></span><span style="display:flex;"><span>        orderDao<span style="color:#f92672">.</span><span style="color:#a6e22e">save</span><span style="color:#f92672">(</span>order<span style="color:#f92672">);</span>
</span></span><span style="display:flex;"><span>        log<span style="color:#f92672">.</span><span style="color:#a6e22e">info</span><span style="color:#f92672">(</span><span style="color:#e6db74">&#34;创建订单成功,订单信息为{}&#34;</span><span style="color:#f92672">,</span> JSON<span style="color:#f92672">.</span><span style="color:#a6e22e">toJSONString</span><span style="color:#f92672">(</span>order<span style="color:#f92672">));</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> order<span style="color:#f92672">;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span></code></pre></div>
    <h4><a href="https://czh-dev.gitee.io/czh-blog.gitee.io/">Back to Home</a></h4>
  </div>

  <div class="span3 bs-docs-sidebar" style="position:fixed;right: 40px;top: 50px;">
    <h1>catalogue</h1>
    <ul class="nav nav-list bs-docs-sidenav">
      <div class="toc-div">
        <nav id="TableOfContents">
  <ul>
    <li><a href="#31-什么是服务治理">3.1 什么是服务治理</a></li>
    <li><a href="#32-常见注册中心">3.2 常见注册中心</a></li>
    <li><a href="#33-nacos-简介">3.3 Nacos 简介</a></li>
    <li><a href="#34-nacos实战入门">3.4 Nacos实战入门</a>
      <ul>
        <li><a href="#341-搭建nacos环境">3.4.1 搭建Nacos环境</a></li>
        <li><a href="#342-将商品服务注册到nacos">3.4.2 将商品服务注册到Nacos</a></li>
        <li><a href="#343-将订单服务注册到nacos">3.4.3 将订单服务注册到Nacos</a></li>
      </ul>
    </li>
  </ul>
</nav>
      </div>
    </ul>
  </div>

</div>
<script src="https://cdn.jsdelivr.net/npm/gumshoejs@5.1.2/dist/gumshoe.min.js"></script>
<script>
  var spy = new Gumshoe('#TableOfContents a', {
    nested: true,
    nestedClass: 'active'
  });
</script>
<style>
   
  #TableOfContents li,
  #TableOfContents ul {
    list-style-type: none;
  }

  #TableOfContents ul {
    padding-left: 0px;
  }

  #TableOfContents li>a {
    display: block;
    padding: 4px 20px;
    font-size: 95%;
    color: #000000;
  }

  #TableOfContents li>a:hover,
  #TableOfContents li>a:focus {
    padding-left: 19px;
    color: #3A6bA5;
    text-decoration: none;
    background-color: transparent;
    border-left: 1px solid #3A6bA5;
  }

  #TableOfContents li.active>a,
  #TableOfContents li.active>a:hover,
  #TableOfContents li.active>a:focus {
    padding-left: 18px;
    font-weight: bold;
    color: #3A6bA5;
    background-color: transparent;
    border-left: 2px solid #3A6bA5;
  }

   
  #TableOfContents li>ul {
    padding-bottom: 10px;
  }

  #TableOfContents li li>a {
    padding-top: 1px;
    padding-bottom: 1px;
    padding-left: 30px;
    font-size: 14px;
    font-weight: normal;
  }

  #TableOfContents li li>a:hover,
  #TableOfContents li li>a:focus {
    padding-left: 29px;
  }

  #TableOfContents li li.active>a,
  #TableOfContents li li.active>a:hover,
  #TableOfContents li li.active>a:focus {
    padding-left: 28px;
    font-weight: 500;
  }

  #TableOfContents .nav-link.active+ul {
    display: block;
  }

  #TableOfContents li>ul {
    display: none;
  }

  #TableOfContents li.active>ul {
    display: inherit;
  }

  .toc-div {
    position: -webkit-sticky;
     
    position: sticky;
     
    top: 20px;
  }
</style>


        </div><footer class="container">
    <hr class="soften">
    <p>
    <a href="https://space.bilibili.com/1799809923">Love eating fried pork ribs</a> | 

&copy; 
<a href="http://jmf-portfolio.netlify.com" target="_blank">
    JM Fergeau
</a>
<span id="thisyear">2023</span>

    | My site


        | Built on <a href="//gohugo.io" target="_blank">Hugo</a>

</p>
    <p class="text-center">
        <a href="https://facebook.com">Facebook</a> 
        <a href="https://twitter.com">Twitter</a> 
        <a href="https://linkedin.com">Linkedin</a> 
        <a href="https://github.com">GitHub</a> 
        <a href="https://gitlab.com">GitLab</a>
    </p>
</footer>

</body><link rel="stylesheet" href="/czh-blog.gitee.io/css/bootstrap.css">
<link rel="stylesheet" href="/czh-blog.gitee.io/css/bootstrap-responsive.css">
<link rel="stylesheet" href="/czh-blog.gitee.io/css/style.css">

<script src="/czh-blog.gitee.io/js/jquery.js"></script>
<script src="/czh-blog.gitee.io/js/bootstrap-386.js"></script>
<script src="/czh-blog.gitee.io/js/bootstrap-transition.js"></script>
<script src="/czh-blog.gitee.io/js/bootstrap-alert.js"></script>
<script src="/czh-blog.gitee.io/js/bootstrap-modal.js"></script>
<script src="/czh-blog.gitee.io/js/bootstrap-dropdown.js"></script>
<script src="/czh-blog.gitee.io/js/bootstrap-scrollspy.js"></script>
<script src="/czh-blog.gitee.io/js/bootstrap-tab.js"></script>
<script src="/czh-blog.gitee.io/js/bootstrap-tooltip.js"></script>
<script src="/czh-blog.gitee.io/js/bootstrap-popover.js"></script>
<script src="/czh-blog.gitee.io/js/bootstrap-button.js"></script>
<script src="/czh-blog.gitee.io/js/bootstrap-collapse.js"></script>
<script src="/czh-blog.gitee.io/js/bootstrap-carousel.js"></script>
<script src="/czh-blog.gitee.io/js/bootstrap-typeahead.js"></script>
<script src="/czh-blog.gitee.io/js/bootstrap-affix.js"></script>
<script>
    _386 = { 
        fastLoad: false ,
        onePass: false , 
        speedFactor: 1 
    };

    
    function ThisYear() {
        document.getElementById('thisyear').innerHTML = new Date().getFullYear();
    };
</script>
</html>
